package game;

import java.io.IOException;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;

/**
 * Gameplay arena
 * 
 * @author Simon
 *
 */
public class Arena {
	public static final int T_UNDEFINED = 0;
	public static final int T_FLOOR = 1;
	public static final int T_WALL = 2;
	public static final int T_BLOCK = 3;
	
	public static final int WIDTH = 11;
	public static final int HEIGHT = 15;
	
	public static final int TILESIZE = 20;
	public static final int OFFSET_X = TILESIZE / 2,
							OFFSET_Y = TILESIZE / 2;
	
	TiledLayer tiles;
	Image tileImage;
	Image bg;
	Vector objects;
	Vector aliens;
	
	Player player;
	
	int level;
	
	static int brickIdx = 0;
	static Random random = new Random();
	static int brickInt = random.nextInt();
	
	public Arena(int level) {
		LUMM2_Canvas.resetKeys();
		try {
			tileImage = Image.createImage("/lvl-tiles.png");
			bg = Image.createImage("/lvl-bg.png");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		this.level = level;
		player = new Player();
		changeLevel(this.level);
	}
	
	public void nextLevel() {
		this.level++;
		if (this.level <= 3) {
			changeLevel(this.level);
		} else {
			LUMM2_Game.currentGameState = LUMM2_Game.WIN;
		}
	}
	
	public int getCurrentLevel() {
		return this.level;
	}
	
	private void changeLevel(int level) {
		tiles = new TiledLayer(WIDTH, HEIGHT, tileImage, 20, 20);
		tiles.setPosition(10, 10);
		for (int y=0; y<15; y++) {
			for (int x=0; x<11; x++) {
				if ((x%2) == 1 && (y%2) == 1) {
					tiles.setCell(x, y, T_BLOCK);
				} else if ((x > 1 || y > 1) && brickGen()) {
					tiles.setCell(x, y, T_WALL);
				} else {
					tiles.setCell(x, y, T_FLOOR);
				}
			}
		}
		
		player.resetPosition();
		objects = new Vector();
		aliens = new Vector();
		
		if (level == 1) {
			placeAlien(Alien.T_SLOW_ALIEN);
			placeAlien(Alien.T_SLOW_ALIEN);
			placeAlien(Alien.T_SLOW_ALIEN);
			placePowerup(Powerup.T_BOMB_COUNT);
			placePowerup(Powerup.T_BOMB_RANGE);
			placeExit();
		} else if (level == 2) {
			placeAlien(Alien.T_SLOW_ALIEN);
			placeAlien(Alien.T_SLOW_ALIEN);
			placeAlien(Alien.T_FAST_ALIEN);
			placeAlien(Alien.T_FAST_ALIEN);
			placePowerup(Powerup.T_BOMB_COUNT);
			placePowerup(Powerup.T_BOMB_RANGE);
			placeExit();
		} else if (level == 3) {
			placeAlien(Alien.T_SLOW_ALIEN);
			placeAlien(Alien.T_SLOW_ALIEN);
			placeAlien(Alien.T_FAST_ALIEN);
			placeAlien(Alien.T_FAST_ALIEN);
			placeAlien(Alien.T_FAST_ALIEN);
			placeAlien(Alien.T_FAST_ALIEN);
			placePowerup(Powerup.T_BOMB_COUNT);
			placePowerup(Powerup.T_BOMB_RANGE);
			placePowerup(Powerup.T_BOMB_COUNT);
			placePowerup(Powerup.T_BOMB_RANGE);
			placeExit();
		}
	}
	
	private void placeAlien(int type) {
		int x, y;
		do {
			x = Math.abs(random.nextInt()) % (WIDTH - 2) + 2;
			y = Math.abs(random.nextInt()) % (HEIGHT - 2) + 2;
		} while (((x%2) == 1 && (y%2) == 1) || isTileBlocked(x, y) || hasAlien(x, y));
		Alien alien = new Alien(type, x, y);
		//System.err.println("place alien: " + x + " " + y);
		aliens.addElement(alien);
	}
	
	private void placePowerup(int type) {
		int x, y;
		do {
			x = Math.abs(random.nextInt()) % WIDTH;
			y = Math.abs(random.nextInt()) % HEIGHT;
		} while (tiles.getCell(x, y) != T_WALL || getPowerupAt(x, y) != null || getExitAt(x, y) != null);
		Powerup powerup = new Powerup(type, x, y);
		//System.err.println("place powerup: " + x + " " + y);
		objects.addElement(powerup);
	}
	
	private void placeExit() {
		int x, y;
		do {
			x = Math.abs(random.nextInt()) % WIDTH;
			y = Math.abs(random.nextInt()) % HEIGHT;
		} while (tiles.getCell(x, y) != T_WALL || getPowerupAt(x, y) != null || getExitAt(x, y) != null);
		Exit exit = new Exit(x, y);
		objects.addElement(exit);
	}
	
	private boolean brickGen() {
		if (brickIdx > 31) {
			brickIdx = 0;
			brickInt = random.nextInt();
		}
		boolean isBrick = ((brickInt >> brickIdx)&1) == 1;
		brickIdx++;
		return isBrick; 
	}
	
	/**
	 * Collision response using the Separating Axis Theorem
	 * @param p
	 * @param tileRect
	 * @return true if player can't move in any direction (sliding counts as moving)
	 */
	private boolean playerWallCollisionResponse(Player p, int tile_x, int tile_y) {
		final int SLIDE_TOLERANCE = 5; 
		
		Rect r = Player.COLLISION_RECT;
		Rect tileRect = new Rect(tile_x * Arena.TILESIZE, tile_y * Arena.TILESIZE, TILESIZE, TILESIZE);
		Rect playerRect = new Rect((int)p.x + r.x, (int)p.y + r.y, r.w, r.h);
		int dx = tileRect.x - playerRect.x;
		int dy = tileRect.y - playerRect.y;
		int xcoll, ycoll;
		if (playerRect.x < tileRect.x) {
			xcoll = -(playerRect.w - dx);
		} else {
			xcoll = tileRect.w + dx;
		}
		if (playerRect.y < tileRect.y) {
			ycoll = -(playerRect.h - dy);
		} else {
			ycoll = tileRect.h + dy;
		}
		if (Math.abs(xcoll) < Math.abs(ycoll)) {
			p.x += xcoll;
			if (p.y + SLIDE_TOLERANCE < tileRect.y) {
				if (!isTileBlocked(p.getTileX(), (int)(p.y / TILESIZE)) && !isTileBlocked(tile_x, tile_y - 1)) {
					p.y -= 1.5;
				} else {
					return true;
				}
			} else if (p.y - SLIDE_TOLERANCE > tileRect.y){
				if (!isTileBlocked(p.getTileX(), (int)(p.y / TILESIZE) + 1) && !isTileBlocked(tile_x, tile_y + 1)) {
					p.y += 1.5;
				} else {
					return true;
				}
			} else {
				return true;
			}
		} else {
			p.y += ycoll;
			if (p.x + SLIDE_TOLERANCE < tileRect.x) {
				if (!isTileBlocked((int)(p.x / TILESIZE), p.getTileY()) && !isTileBlocked(tile_x - 1, tile_y)) {
					p.x -= 1.5;
				} else {
					return true;
				}
			} else if (p.x - SLIDE_TOLERANCE > tileRect.x){
				if (!isTileBlocked((int)(p.x / TILESIZE) + 1, p.getTileY()) && !isTileBlocked(tile_x + 1, tile_y)) {
					p.x += 1.5;
				} else {
					return true;
				}
			} else {
				return true;
			}
		}
		return false;
	}
	
	public boolean hasAlien(int tile_x, int tile_y) {
		for (Enumeration e = aliens.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Alien) {
				Alien alien = (Alien)o;
				if (alien.getTileX() == tile_x && alien.getTileY() == tile_y) {
					return true;
				}
			} else {
				throw new Error(o + " is not an alien.");
			}
		}
		return false;
	}
	
	public Powerup getPowerupAt(int tile_x, int tile_y) {
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Powerup) {
				Powerup powerup = (Powerup)o;
				if (powerup.tile_x == tile_x && powerup.tile_y == tile_y) {
					return powerup;
				}
			}
		}
		return null;
	}
	
	public Exit getExitAt(int tile_x, int tile_y) {
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Exit) {
				Exit exit = (Exit)o;
				if (exit.tile_x == tile_x && exit.tile_y == tile_y) {
					return exit;
				}
			}
		}
		return null;
	}
	
	public int getAlienCount() {
		return aliens.size();
	}
	
	public boolean isTileBlocked(int tile_x, int tile_y) {
		if (tile_x < 0 || tile_x >= WIDTH || tile_y < 0 || tile_y >= HEIGHT) {
			return true;
		}
		int tile = tiles.getCell(tile_x, tile_y);
		switch (tile) {
		case T_BLOCK: return true;
		case T_WALL: return true;
		}
		for (int i=0; i<objects.size(); ++i) {
			Object o = objects.elementAt(i);
			if (o instanceof ArenaObject) {
				if (o instanceof Bomb) {
					Bomb b = (Bomb)o;
					if (b.tile_x == tile_x && b.tile_y == tile_y) {
						return true;
					}
				} else if (o instanceof Fire) {
					Fire f = (Fire)o;
					if (f.tile_x == tile_x && f.tile_y == tile_y) {
						return true;
					}
				} else if (o instanceof ExplodingWall) {
					ExplodingWall w = (ExplodingWall)o;
					if (w.tile_x == tile_x && w.tile_y == tile_y) {
						return true;
					}
				}
			} else {
				throw new Error(o + " is not an object.");
			}
		}
		return false;
	}
	
	public boolean playerWallCollision(Player p) {
		Rect r = Player.COLLISION_RECT;
		int px = (int)p.x + r.x;
		int py = (int)p.y + r.y;
		int start_x = (px) / TILESIZE;
		int start_y = (py) / TILESIZE;
		int end_x = (px + r.w - 1) / TILESIZE;
		int end_y = (py + r.h - 1) / TILESIZE;
		boolean borderCollide = false;
		if (px < 0) {
			p.x -= px;
			borderCollide = true;
		}
		if (end_x > WIDTH - 1) {
			p.x -= (px + r.w) % TILESIZE;
			borderCollide = true;
		}
		if (py < 0) {
			p.y -= py;
			borderCollide = true;
		}
		if (end_y > HEIGHT - 1) {
			p.y -= (py + r.h) % TILESIZE;
			borderCollide = true;
		}
		if (borderCollide) return borderCollide;
		for (int tile_y=start_y; tile_y<=end_y; tile_y++) {
			for (int tile_x=start_x; tile_x<=end_x; tile_x++) {
				int tile = tiles.getCell(tile_x, tile_y);
				switch (tile) {
				case T_BLOCK: return playerWallCollisionResponse(p, tile_x, tile_y);
				case T_WALL: return playerWallCollisionResponse(p, tile_x, tile_y);
				}
				for(int i=0; i<objects.size(); ++i) {
					if (objects.elementAt(i) instanceof Bomb) {
						//Bomb b1 = (Bomb)objects.elementAt(i);
						//if (b1.tile_x == tile_x && b1.tile_y == tile_y) {
						//	playerWallCollisionResponse(p, tileRect);
						//	return true;
						//}
					} else if (objects.elementAt(i) instanceof ExplodingWall) {
						ExplodingWall w1 = (ExplodingWall)objects.elementAt(i);
						if (w1.tile_x == tile_x && w1.tile_y == tile_y) {
							return playerWallCollisionResponse(p, tile_x, tile_y);
						}
					}
				}
			}
		}
		return false;
	}
	
	public void playerPowerupCollision(Player p) {
		for (int i=0; i<objects.size(); ++i) {
			if (objects.elementAt(i) instanceof Powerup) {
				Powerup powerup = (Powerup)objects.elementAt(i);
				if (p.getTileX() == powerup.tile_x && p.getTileY() == powerup.tile_y) {
					p.eatPowerup(powerup);
					objects.removeElementAt(i);
					return;
				}
			}
		}
	}
	
	public void playerExitCollision(Player p) {
		for (int i=0; i<objects.size(); ++i) {
			if (objects.elementAt(i) instanceof Exit) {
				Exit exit = (Exit)objects.elementAt(i);
				if (p.getTileX() == exit.tile_x && p.getTileY() == exit.tile_y) {
					//System.err.println("alien count. " + getAlienCount());
					if (getAlienCount() == 0) {
						player.addScore(300);
						nextLevel();
						LUMM2_Game.currentGameState = LUMM2_Game.START;
					}
				}
			}
		}
	}
	
	public void placeBomb(Player p) {
		int off_x = Player.COLLISION_RECT.x + Player.COLLISION_RECT.w / 2;
		int off_y = Player.COLLISION_RECT.y + Player.COLLISION_RECT.h / 2;
		int tile_x = ((int)p.x + off_x) / TILESIZE;
		int tile_y = ((int)p.y + off_y) / TILESIZE;
		for(int i=0; i<objects.size(); ++i) {
			if (objects.elementAt(i) instanceof Bomb) {
				Bomb b1 = (Bomb)objects.elementAt(i);
				if (b1.tile_x == tile_x && b1.tile_y == tile_y) {
					return; // Don't place bomb over existing bomb
				}
			}
		}
		Bomb b = new Bomb(tile_x, tile_y);
		objects.addElement(b);
		player.putBomb();
	}
	
	public void advanceObjects() {
		Vector toDelete = new Vector();
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof ArenaObject) {
				ArenaObject object = (ArenaObject)o;
				object.advance();
				if (object.isDead()) {
					toDelete.addElement(object);
				}
			}
		}
		for (Enumeration e = toDelete.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			objects.removeElement(o);
			if (o instanceof Bomb) {
				explodeBomb((Bomb)o);
			}
		}
	}
	
	public void advanceAliens() {
		Vector toDelete = new Vector();
		for (Enumeration e = aliens.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Alien) {
				Alien alien = (Alien)o;
				alien.logic(this);
				if (alien.dead == 0 && alien.getTileX() == player.getTileX() && alien.getTileY() == player.getTileY()) {
					player.kill();
				}
				if (alien.isDead()) {
					toDelete.addElement(alien);
				}
			} else {
				throw new Error(o + " is not an alien.");
			}
		}
		for (Enumeration e = toDelete.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			aliens.removeElement(o);
		}
	}
	
	public void drawObjects(Graphics g) {
		/* Draw powerups first */
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof ArenaObject && o instanceof Powerup) {
				ArenaObject object = (ArenaObject)o;
				object.draw(g);
			}
		}
		
		/* Draw all objects except powerups */
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof ArenaObject) {
				if (!(o instanceof Powerup)) {
					ArenaObject object = (ArenaObject)o;
					object.draw(g);
				}
			} else {
				throw new Error(o + " is not an object.");
			}
		}
	}
	
	public void drawAliens(Graphics g) {
		for (Enumeration e = aliens.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Alien) {
				Alien alien = (Alien)o;
				alien.draw(g);
			} else {
				throw new Error(o + " is not an alien.");
			}
		}
	}
	
	public void destroyWall(int tile_x, int tile_y) {
		tiles.setCell(tile_x, tile_y, T_FLOOR);
		objects.addElement(new ExplodingWall(tile_x, tile_y));
		Powerup powerup = getPowerupAt(tile_x, tile_y);
		if (powerup != null) {
			powerup.setVisible(true);
		}
		Exit exit = getExitAt(tile_x, tile_y);
		if (exit != null) {
			exit.setVisible(true);
		}
		player.addScore(10);
	}
	
	int getTile(int x, int y) {
		if (x < 0 || x >= WIDTH || y < 0 || y >= HEIGHT) return T_UNDEFINED;
		return tiles.getCell(x, y);
	}
	
	Fire getFireAt(int tile_x, int tile_y) {
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Fire) {
				Fire f = (Fire)o;
				if (f.tile_x == tile_x && f.tile_y == tile_y) {
					return f;
				}
			}
		}
		return null;
	}
	
	Bomb getBombAt(int tile_x, int tile_y) {
		for (Enumeration e = objects.elements(); e.hasMoreElements(); ) {
			Object o = e.nextElement();
			if (o instanceof Bomb) {
				Bomb b = (Bomb)o;
				if (b.tile_x == tile_x && b.tile_y == tile_y) {
					return b;
				}
			}
		}
		return null;
	}
	
	/**
	 * 
	 * @param d
	 * @param tile_x
	 * @param tile_y
	 * @return false in case the fire got blocked by an obstacle, else true
	 */
	boolean placeFire(int d, int tile_x, int tile_y) {
		int t = getTile(tile_x, tile_y);
		if (t == T_FLOOR) {
			if (player.getTileX() == tile_x && player.getTileY() == tile_y) {
				player.kill();
			}
			for (Enumeration e = aliens.elements(); e.hasMoreElements(); ) {
				Object o = e.nextElement();
				if (o instanceof Alien) {
					Alien alien = (Alien)o;
					if (alien.getTileX() == tile_x && alien.getTileY() == tile_y) {
						alien.kill();
						if (alien.type == Alien.T_FAST_ALIEN) player.addScore(200);
						if (alien.type == Alien.T_SLOW_ALIEN) player.addScore(50);
					}
				} else {
					throw new Error(o + " is not an alien.");
				}
			}
			Bomb b = getBombAt(tile_x, tile_y);
			Fire f = getFireAt(tile_x, tile_y);
			if (b != null) {
				b.dead = true;
				return false;
			} else if (f != null) {
				return false;
			} else {
				objects.addElement(new Fire(d, tile_x, tile_y));
				return true;
			}
		} else {
			if (t == T_WALL) {
				destroyWall(tile_x, tile_y);
			}
			return false;
		}
	}
	
	public void explodeBomb(Bomb b) {
		LUMM2_Game.wavPlayer.play("/explosion.wav");
		int x0 = b.tile_x;
		int y0 = b.tile_y;
		int maxDistance = player.getBombStrength();
		placeFire(Fire.MID, x0, y0);
		int distance = maxDistance;
		for (int x = x0 - 1; x >= 0; --x) {
			distance--;
			if (distance <= 0) {
				placeFire(Fire.LEFT, x, y0); break;
			}
			if (!placeFire(Fire.HORIZONTAL, x, y0)) break;
		}
		distance = maxDistance;
		for (int x = x0 + 1; x < WIDTH; ++x) {
			distance--;
			if (distance <= 0) {
				placeFire(Fire.RIGHT, x, y0); break;
			}
			if (!placeFire(Fire.HORIZONTAL, x, y0)) break;
		}
		distance = maxDistance;
		for (int y = y0 - 1; y >= 0; --y) {
			distance--;
			if (distance <= 0) {
				placeFire(Fire.UP, x0, y); break;
			}
			if (!placeFire(Fire.VERTICAL, x0, y)) break;
		}
		distance = maxDistance;
		for (int y = y0 + 1; y < HEIGHT; ++y) {
			distance--;
			if (distance <= 0) {
				placeFire(Fire.DOWN, x0, y); break;
			}
			if (!placeFire(Fire.VERTICAL, x0, y)) break;
		}
		player.returnBomb();
	}
	
	public static void Position(Sprite gfx, int tile_x, int tile_y) {
		gfx.setPosition(tile_x * TILESIZE + OFFSET_X, tile_y * TILESIZE + OFFSET_Y);
	}
	
	boolean helpLock = true;
	
	public void logic() {
		if (!LUMM2_Canvas.key0) {
			helpLock = false;
		}
		if (LUMM2_Canvas.key0 && !helpLock) {
			LUMM2_Game.help = new HelpScreen();
			LUMM2_Game.currentGameState = LUMM2_Game.HELP;
			helpLock = true;
			return;
		}
		
		player.input(this);
		advanceAliens();
		advanceObjects();
	}
	
	public void draw(Graphics g) {
		g.drawImage(bg, 0, 0, Graphics.TOP|Graphics.LEFT);
		tiles.paint(g);
		drawObjects(g);
		drawAliens(g);
		player.draw(g);
	}

	public void gameOver() {
		LUMM2_Game.currentGameState = LUMM2_Game.GAME_OVER;
	}
}
